/*
* Copyright 2010 Pavel Kozhin 
*
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed  under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License.
*
* Author: Pavel Kozhin.
*/

package org.bookmarksframework.navigation.state
{
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.utils.Dictionary;
	
	import org.bookmarksframework.navigation.events.NavigationConditionChangeEvent;
	import org.bookmarksframework.navigation.events.NavigationConditionValidateEvent;
	import org.bookmarksframework.navigation.state.IStateController;
	import org.bookmarksframework.navigation.state.StateControllerItem;
	
	[Event(name="conditionChange", type="org.bookmarksframework.navigation.events.NavigationConditionChangeEvent")]
	
	[Event(name="conditionValidate", type="org.bookmarksframework.navigation.events.NavigationConditionValidateEvent")]
	
	public class StateController extends EventDispatcher implements IStateController
	{
		protected var states:Dictionary;
		
		protected var builder:StateBuilder;
		
		public function initialize(map:XML, lazy:Boolean = true):void
		{
			destroy();
			
			builder = new StateBuilder(map);
			states = new Dictionary();
			
			if (!lazy)
			{
				var s:Array = builder.getAllStates();
				var sLen:int = s.length;
				
				for (var i:int = 0; i < sLen; ++i)
					states[NavigationState(s[i]).id] = s[i]; 
			}
		}
		
		public function getState(stateOrPath:String):NavigationState
		{
			var result:NavigationState = null;
			var controllerItem:StateControllerItem;
			var item:StateBuilderItem;
			
			if (stateOrPath)
			{
				var stateId:String = builder.getStateIdByPath(stateOrPath) || stateOrPath;
				
				if (states[stateId])
				{
					controllerItem = StateControllerItem(states[stateId]);
					
					controllerItem.state.setPath((stateOrPath == controllerItem.state.id) ?
						controllerItem.state.paths.getItemAt(0) as String : stateOrPath);
					
					return controllerItem.state;
				}
				
				if (builder.validateState(stateOrPath))
				{
					item = builder.byState(stateOrPath);
				}
				else if (builder.validatePath(stateOrPath))
				{
					item = builder.byPath(stateOrPath);
				}
				
				if (!item)
					throw new Error("StateController Error: Can't build NavigationState. " +
						"There is no state or path \""+stateOrPath+"\" in states map or more than one.");
				
				result = new NavigationState(this, item.name, item.path, item.paths, 
					item.links, item.attributes.strict == "true", 
					item.attributes.enabled != "false", {}, 
					item.attributes.strict != "true", item.attributes);
				states[item.name] = new StateControllerItem(result, new Dictionary());
			}
			
			return result;
		}
		
		public function getCondition(state:NavigationState, condition:String):*
		{
			if (!state || !condition)
				return null;
			
			var item:StateControllerItem = getStateItem(state.id);
			
			return item.conditions[condition];
		}
		
		public function setCondition(state:NavigationState, condition:String, data:*):void
		{
			if (!state || !condition)
				return;
			
			var item:StateControllerItem = getStateItem(state.id);
			
			var oldVal:* = item.conditions[condition];
			item.conditions[condition] = data;
			validate(state);
			
			var evt:NavigationConditionChangeEvent = 
				new NavigationConditionChangeEvent(item.state, condition, 
					oldVal, data);
			state.dispatchEvent(evt);
			dispatchEvent(evt.clone());
		}
		
		public function removeCondition(state:NavigationState, condition:String):void
		{
			if (!state || !condition)
				return;
			
			var item:StateControllerItem = getStateItem(state.id);
			var oldVal:* = item.conditions[condition];
			delete item.conditions[condition];
			
			validate(state);
			
			var evt:NavigationConditionChangeEvent = 
				new NavigationConditionChangeEvent(item.state, condition, 
					oldVal, undefined);
			
			state.dispatchEvent(evt);
			dispatchEvent(evt.clone());
		}
		
		public function hasConditions(state:NavigationState):Boolean
		{
			if (!state)
				return false;
			
			var item:StateControllerItem = getStateItem(state.id);
			
			for (var name:String in item.conditions)
				return true;
			
			return false;
		}
		
		public function validate(state:NavigationState = null, condition:String = ""):Boolean
		{
			var result:Boolean = true;
			
			if (state)
			{
				if (!condition)
					result = validateState(state);
				else
					result = validateCondition(getStateItem(state.id), condition);
			}
			else
			{
				for each(var item:Object in states)
				{
					if (!validateState(NavigationState(item)))
						result = false
				}
			}
			
			state.setIsValid(result);
			
			return result;
		}
		
		public function destroy():void
		{
			builder = null;
			
			for each(var item:Object in states)
				NavigationState(item).destroy();
			
			states = null;
		}
		
		protected function getStateItem(state:String):StateControllerItem
		{
			var result:StateControllerItem = null;
			
			if (state && states[state])
				result = StateControllerItem(states[state]);
			
			return result;
		}
		
		protected function validateState(state:NavigationState):Boolean
		{
			var item:StateControllerItem;
			var conditions:Dictionary;
			var value:*;
			
			if (state)
			{
				item = getStateItem(state.id);
				
				if (item)
				{
					conditions = item.conditions;
					
					for (value in conditions)
					{
						if (!validateCondition(item, value as String))
							return false;
					}
				}
				
				return !(state.strict && !hasConditions(state));
			}
			
			return false;
		}
		
		protected function validateCondition(item:StateControllerItem, 
											 condition:String):Boolean
		{
			var result:Boolean = true;
			var evt:NavigationConditionValidateEvent;
			
			if (item)
			{
				var value:* = item.conditions[condition];
				result = Boolean(value);
				
				try {
					if (value is Function)
						result = Boolean(value(item.state, condition));
				} catch (e:Error) {
					result = false;
				}
			}
			
			evt = new NavigationConditionValidateEvent(item.state, condition, result);
			
			item.state.dispatchEvent(evt);
			dispatchEvent(evt.clone());
			
			return result;
		}
		
	}
}